package br.gfca.simplenet;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import br.gfca.simplenet.util.Printer;

public class ClientImpl {
	
	private ClientListener listener;
	
	private Socket socket;
	private ObjectOutputStream out;
	private ObjectInputStream in;
	
	public ClientImpl( ClientListener listener ) {
		this.listener = listener;
	}
	
	public void connect( final String host, final int port) {
		
		if ( this.isConnected() ) {
			Printer.getInstance().printError("Attempt to connect an already connected socket.");
			return;
		}
		
		Thread t = new Thread( new Runnable() {
			@Override
			public void run() {
				try {
					socket = new Socket( host, port );
					in = new ObjectInputStream( socket.getInputStream() );	
					out = new ObjectOutputStream( socket.getOutputStream() );

					listener.connected();
					beginAcceptMessages();
				}
				catch (UnknownHostException uhe) {
					listener.connectionFailed( new ConnectionFailureEvent( "Unknown host \"" + uhe.getMessage() + "\"" ) );
					Printer.getInstance().printError("Failed to connect. Unknown host:");
					Printer.getInstance().printStack( uhe );
				}
				catch (IOException ioe) {
					listener.connectionFailed( new ConnectionFailureEvent( ioe.getMessage() ) );
					Printer.getInstance().printError("Failed to connect:");
					Printer.getInstance().printStack( ioe );
				}
				catch (RuntimeException re) {
					listener.connectionFailed( new ConnectionFailureEvent( re.getMessage() ) );
					Printer.getInstance().printError("Failed to connect:");
					Printer.getInstance().printStack( re );
				}
			}
		});
		t.start();
	}
	
	public void disconnect() {
		if ( this.socket != null && !this.socket.isClosed() ) {
			try {
				this.socket.close();
			}
			catch (IOException ioe) {
				Printer.getInstance().printError("Failed to close socket:");
				Printer.getInstance().printStack( ioe );
			}
		}
		
		this.socket = null;
		this.in = null;
		this.out = null;
	}
	
	private boolean isConnected() {
		return this.socket != null && this.socket.isConnected();
	}
	
	public void sendMessage( final String message ) {
		new Thread( new Runnable() {
			@Override
			public void run() {
				if ( isConnected() ) {
					try {
						out.writeUTF( message );
						out.flush();
						listener.messageSent( message );
					}
					catch (IOException ioe) {
						// TODO tirar bug do send nick infinito
						Printer.getInstance().printError("Failed to send message:");
						Printer.getInstance().printStack( ioe );
						listener.messageFailed( message );
					}
				}	
			}			
		}).start();
	}
	
	private void beginAcceptMessages() {
		Thread t = new Thread( new Runnable() {
			@Override
			public void run() {
				while ( isConnected() ) {
					try {
						String message = in.readUTF();
						listener.messageReceived( message );
					}
					catch (SocketException se) {
						// Client disconnected itself and the socket was closed.
						// There is no need to alert listener.
						Printer.getInstance().printError("Socket has been closed by client:");
						Printer.getInstance().printStack( se );
						return;
					}
					catch (EOFException eofe) {
						// Socket was closed by server (server kicked client or server is down)
						// Alert listener of client disconnection and complete disconnection
						Printer.getInstance().printError("Socket has been closed by server:");
						Printer.getInstance().printStack( eofe );
						disconnect();
						listener.connectionLost();
						return;
					}
					catch (IOException ioe) {
						// Unknown input error. Socket will be disconnected
						// and listener will be warned.
						Printer.getInstance().printError("Unknown input error. Socket will be disconnected:");
						Printer.getInstance().printStack( ioe );
						disconnect();
						listener.connectionLost();
						return;
					}
				}
			}
		});
		t.start();
	}
}